﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.IO;
using System.Security.Cryptography;
using System.Net;
using System.Net.Sockets;
using System.Globalization;
using System.Reflection;
using System.Xml;
using System.Runtime.InteropServices;

namespace GPSou
{
    public partial class FormDatos : Form
    {
        public string usuario = null;
        public string clave = null;
        public uint refreshTime = 0;
        public uint interval = 0;
        public string puerto = null;
        public byte[] header;
        public byte[] key = new byte[16];

        delegate void SetText(Control control, string str);
        delegate void OnOffGPS();
        delegate void Alerta(string texto);

        Configuracion form1 = null;
        FormRefresh intervalFrm = null;
        FormTransfer progressFrm = null;
        AcercaDe acercaDeFrm = null;
        

        private bool enviar = false;
        private bool iniciarTX = false;
        private bool guardar = false;
        private bool closeDataFile = false;
        private float diffTMedia;
        private float distancia;
        private int bytesTx = 0;

        ManualResetEvent m_EventStopThread = new ManualResetEvent(false);
        ManualResetEvent m_EventThreadStopped = new ManualResetEvent(false);
        Thread t = null;

        [DllImport("CoreDll.dll")] public static extern void SystemIdleTimerReset();

        public FormDatos()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            form1 = new Configuracion();
            intervalFrm = new FormRefresh();
            progressFrm = new FormTransfer();
            acercaDeFrm = new AcercaDe();

            confMenu_Click(null, null);

            string appFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
            string confFile = Path.Combine(appFolder, "config.xml");

            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load(confFile);

            XmlNode paramNode;
            paramNode = xmlDoc.SelectSingleNode("/config/param[@nombre='recordOn']");
            if (paramNode.Attributes["valor"].Value.CompareTo("true") == 0)
            {
                guardar = true;
                guardarMenu.Text = "Desactivar";
                guardarMenu.Checked = true;
            }
            paramNode = xmlDoc.SelectSingleNode("/config/param[@nombre='interval']");
            intervalFrm.intervalCtrl.Text = paramNode.Attributes["valor"].Value;
            interval = UInt32.Parse(paramNode.Attributes["valor"].Value) * 1000;
        }
 
        private void label1_ParentChanged(object sender, EventArgs e)
        {

        }

        private void Form1_KeyDown(object sender, KeyEventArgs e)
        {
            if ((e.KeyCode == System.Windows.Forms.Keys.Up))
            {
                // Rocker Up
                // Up
            }
            if ((e.KeyCode == System.Windows.Forms.Keys.Down))
            {
                // Rocker Down
                // Down
            }
            if ((e.KeyCode == System.Windows.Forms.Keys.Left))
            {
                // Left
            }
            if ((e.KeyCode == System.Windows.Forms.Keys.Right))
            {
                // Right
            }
            if ((e.KeyCode == System.Windows.Forms.Keys.Enter))
            {
                // Enter
            }

        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (t == null)
            {
                serialPort.PortName = puerto;

                m_EventStopThread.Reset();
                m_EventThreadStopped.Reset();
                ThreadStart ts = new ThreadStart(leeDatos);
                t = new Thread(ts);
                t.Start();
            }
            else
            {
                m_EventStopThread.Set();
                while (!m_EventThreadStopped.WaitOne(0, false))
                    Application.DoEvents();
            }
        }


        public static byte[] getDigets(string str)
        {
            SHA1 sha = new SHA1CryptoServiceProvider();
            return(sha.ComputeHash(Encoding.UTF8.GetBytes(str)));
        }

        private byte[] cifrar(byte[] clave, byte[] datos)
        {
            MemoryStream datosCifrados = new MemoryStream();

            RijndaelManaged myRijndael = new RijndaelManaged();
            myRijndael.Mode = CipherMode.ECB;
            ICryptoTransform cipher = myRijndael.CreateEncryptor(clave, new byte[0]);

            CryptoStream csEncrypt = new CryptoStream(datosCifrados, cipher, CryptoStreamMode.Write);
            csEncrypt.Write(datos, 0, datos.Length);
            csEncrypt.FlushFinalBlock();
            return(datosCifrados.ToArray());
        }

        private void CreateEncryptor(byte[] clave, object p)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        void leeDatos()
        {
            int b1, b2;
            string linea;
            char[] lineaChar;
            bool datosValidos;
            float latitudNMEA = 0.0f, longitudNMEA = 0.0f, velocidad = 0.0f, rumbo = 0.0f, altitud = 0.0f;
            string timestamp = "";
            float latitud, latitudAnterior = 0.0f, longitud, longitudAnterior = 0.0f, grados;
            float velocidadMedia = 0.0f;
            bool primerDato = true;
            float dx, dy;

            DateTime time = DateTime.Now;
            DateTime timeGuardar = time;
            DateTime tmpTime;
            TimeSpan diff;

            StreamWriter dataFile = null;

            int paddingLen;
            string paddingStr = "               ";

            short checksum=0;
            DateTime tMediaAnterior = DateTime.Now, tMedia;
            diffTMedia = 0;
            distancia = 0;

            NumberFormatInfo nfi = new NumberFormatInfo();
            nfi.NumberDecimalSeparator = ".";

            try
            {
                serialPort.Open();
            }
            catch (Exception) { }

            if (serialPort.IsOpen)
            {
                onGPS();
            }
            else
            {
                alerta("Fallo de comunicación con el dispositivo GPS");
                m_EventStopThread.Set();
            }

            IPEndPoint destino = new IPEndPoint(IPAddress.Parse(SERVER), 5900);
            //                IPEndPoint destino = new IPEndPoint(0x1C424C54, 5900);
            try
            {
                while (!m_EventStopThread.WaitOne(0, false))
                {
                    try
                    {
                        SystemIdleTimerReset();
                    }
                    catch (Exception e) { }

                    try
                    {
                        linea = serialPort.ReadLine();
                    }
                    catch (Exception e)
                    {
                        try
                        {
                            setText(EstadoGPS, "Problema de conexión con GPS");
                            if (serialPort.IsOpen)
                            {
                                serialPort.Close();
                            }
                            serialPort.Open();
                            continue;
                        }
                        catch (Exception)
                        {
                        }
                        continue;
                    }

                    tMedia = DateTime.Now;

                    checksum=0;
                    b2 = -1;
                    b1 = linea.IndexOf('$');
                    if (b1 != -1)
                        b2 = linea.IndexOf('*', b1);
                    if (b2 == -1)
                        continue;

                    lineaChar=linea.ToCharArray();

                    if (closeDataFile)
                    {
                        if (dataFile != null)
                        {
                            dataFile.Close();
                            dataFile = null;
                        }
                        closeDataFile = false;
                    }

                    if (iniciarTX)
                    {
                        String respuesta = "";
                        try
                        {
                            HttpWebRequest httpReq = (HttpWebRequest)WebRequest.Create("http://SERVER/GoogleGPS/ServletGoogleGPS?p=login&login=" + usuario + "&password=" + clave + "&check=true");
                            HttpWebResponse webResp = (HttpWebResponse)httpReq.GetResponse();
                            Stream receiveStream = webResp.GetResponseStream();
                            StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8);
                            respuesta = readStream.ReadToEnd();
                            webResp.Close();
                        }
                        catch (Exception) { }

                        if (respuesta.CompareTo("OK") == 0)
                        {
                            onTX();
                        }
                        else if (respuesta.CompareTo("ERROR AUTENTICACION") == 0)
                        {
                            alerta("ERROR: Usuario y clave no válidos.");
                        }
                        else
                        {
                            alerta("ERROR: El servidor no responde.");
                        }

                        iniciarTX = false;
                    }

                    try
                    {
                        for (int i = b1 + 1; i < b2; i++)
                            checksum ^= (short)lineaChar[i];
                        if (checksum != Convert.ToInt16(linea.Substring(b2 + 1, 2), 16))
                            continue;
                    }
                    catch (Exception)
                    {
                        continue;
                    }



                    if (linea.IndexOf("$GPGGA") != -1)
                    {
                        b1 = linea.IndexOf(',');
                        b1 = linea.IndexOf(',', b1 + 1);
                        b1 = linea.IndexOf(',', b1 + 1);
                        b1 = linea.IndexOf(',', b1 + 1);
                        b1 = linea.IndexOf(',', b1 + 1);
                        b1 = linea.IndexOf(',', b1 + 1);

                        if (linea.ToCharArray()[b1 + 1] != '0')
                        {
                            b1 = linea.IndexOf(',', b1 + 1);
                            b1 = linea.IndexOf(',', b1 + 1);
                            b1 = linea.IndexOf(',', b1 + 1);
                            b2 = linea.IndexOf(',', b1 + 1);
                            try
                            {
                                altitud = float.Parse(linea.Substring(b1 + 1, b2 - b1 - 1), nfi);
                            }
                            catch (FormatException)
                            {
                                altitud = 0.0f;
                            }
                        }
                    }

                    if (linea.IndexOf("$GPRMC") != -1)
                    {
                        b1 = linea.IndexOf(',');
                        b2 = linea.IndexOf(',', b1 + 1);
                        if (b2 > b1)
                        {
                            timestamp = linea.Substring(b1 + 1, b2 - b1 - 1).Replace(".", null);
                        }

                        b1 = b2;
                        lineaChar = linea.ToCharArray();
                        if (lineaChar[b1 + 1] == 'V')
                        {
                            setText(EstadoGPS, "POSICIÓN GPS NO VÁLIDA");
                            datosValidos = false;
                        }
                        else
                        {
                            if (enviar)
                                setText(EstadoGPS, "Enviando datos");
                            else
                                setText(EstadoGPS, "");
                            datosValidos = true;
                        }

                        b1 = linea.IndexOf(',', b1 + 1);
                        b2 = linea.IndexOf(',', b1 + 1);


                        if (b2 > b1)
                            try
                            {
                                latitudNMEA = float.Parse(linea.Substring(b1 + 1, b2 - b1 - 1), nfi);
                            }
                            catch (FormatException)
                            {
                                latitudNMEA = 0.0f;
                            }
                        b1 = b2;
                        if (lineaChar[b1 + 1] == 'S')
                            latitudNMEA = -latitudNMEA;
                        b1 = linea.IndexOf(',', b1 + 1);
                        b2 = linea.IndexOf(',', b1 + 1);


                        if (b2 > b1)
                            try
                            {
                                longitudNMEA = float.Parse(linea.Substring(b1 + 1, b2 - b1 - 1), nfi);
                            }
                            catch (FormatException)
                            {
                                longitudNMEA = 0.0f;
                            }
                        b1 = b2;
                        if (lineaChar[b1 + 1] == 'W')
                            longitudNMEA = -longitudNMEA;
                        b1 = linea.IndexOf(',', b1 + 1);
                        b2 = linea.IndexOf(',', b1 + 1);

                        if (b2 > b1)
                            try
                            {
                                velocidad = float.Parse(linea.Substring(b1 + 1, b2 - b1 - 1), nfi);
                            }
                            catch (FormatException)
                            {
                                velocidad = 0.0f;
                            }
                        velocidad *= 1.852f;
                        b1 = b2;
                        b2 = linea.IndexOf(',', b1 + 1);

                        if (b2 > b1)
                            try
                            {
                                rumbo = float.Parse(linea.Substring(b1 + 1, b2 - b1 - 1), nfi);
                            }
                            catch (FormatException)
                            {
                                rumbo = 0.0f;
                            }
                        b1 = b2;
                        b2 = linea.IndexOf(',', b1 + 1);
                        if (b2 > b1)
                            timestamp = linea.Substring(b1 + 1, b2 - b1 - 1) + timestamp;

                        latitudNMEA = latitudNMEA * 10000.0f;
                        longitudNMEA = longitudNMEA * 10000.0f;
                        velocidad = (float)Math.Floor(velocidad * 10.0f);
                        rumbo = rumbo = 255.0f / 360.0f;

                        if (datosValidos)
                        {
                            latitud = latitudNMEA / 1000000.0f;
                            grados = (float)Math.Floor(latitud);
                            if (grados < 0)
                                grados++;
                            latitud = (latitud - grados) / 0.6f + grados;

                            longitud = longitudNMEA / 1000000.0f;
                            grados = (float)Math.Floor(longitud);
                            if (grados < 0)
                                grados++;
                            longitud = (longitud - grados) / 0.6f + grados;

                            if (primerDato)
                                primerDato = false;
                            else
                            {
                                dx = (float)(6370 * (latitud - latitudAnterior) * Math.PI / 180);
                                dy = (float)(6370 * Math.Cos(latitud * Math.PI / 180) * (longitud - longitudAnterior) * Math.PI / 180);
                                distancia += (float)Math.Sqrt(dx * dx + dy * dy);
                                
                                diffTMedia += (float)(tMedia - tMediaAnterior).TotalMilliseconds;
                                velocidadMedia = (float)Math.Floor(0.5 + 3600000000 * distancia / diffTMedia) / 1000;
                            }
                            latitudAnterior = latitud;
                            longitudAnterior = longitud;

                            tMediaAnterior = tMedia;



                            linea = null;
                            tmpTime = DateTime.Now;

                            if (guardar)
                            {
                                if (dataFile == null)
                                {
                                    string appFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
                                    string dataFileStr = Path.Combine(appFolder, "datos.txt");
                                    dataFile = File.AppendText(dataFileStr);
                                }

                                diff = tmpTime - timeGuardar;
                                if (diff.TotalMilliseconds >= interval)
                                {
                                    timeGuardar = tmpTime;

                                    linea = "";
                                    linea += (int)latitudNMEA;
                                    linea += ";" + (int)longitudNMEA;
                                    linea += ";" + (int)velocidad;
                                    linea += ";" + (int)rumbo;
                                    linea += ";" + (int)altitud;
                                    linea += ";" + timestamp;

                                    dataFile.WriteLine(linea);
                                }
                            }

                            if (enviar)
                            {
                                diff = tmpTime - time;
                                if (diff.TotalMilliseconds >= refreshTime)
                                 {
                                    time = tmpTime;

                                    if (linea == null)
                                    {
                                        linea = "";
                                        linea += (int)latitudNMEA;
                                        linea += ";" + (int)longitudNMEA;
                                        linea += ";" + (int)velocidad;
                                        linea += ";" + (int)rumbo;
                                        linea += ";" + (int)altitud;
                                        linea += ";" + timestamp;
                                    }

                                    try
                                    {   
                                        MemoryStream outStream = new MemoryStream();
                                        outStream.Write(header, 0, header.Length);

                                        paddingLen = 16 - linea.Length % 16;
                                        if (paddingLen == 16)
                                            paddingLen = 0;

                                        linea += paddingStr.Substring(0, paddingLen);
                                        outStream.Write(cifrar(key, Encoding.UTF8.GetBytes(linea)), 0, linea.Length);
                                        bytesTx += 28 + (int)outStream.Length;
                                        setText(InfoBytes, bytesTx / 1000.0f + " kB");

                                        UdpClient con = new UdpClient();
                                        con.Connect(destino);
                                        con.Send(outStream.GetBuffer(), (int)outStream.Length);
                                        con.Close();

                                    }
                                    catch (IOException)
                                    {
                                        setText(EstadoGPS, "FALLO DE COMUNICACIÓN CON EL SERVIDOR");
                                    }
                                }
                            }
                        }

                        setText(InfoVelocidad, velocidad / 10.0f + " km/h");
                        setText(InfoDistancia, Math.Floor(0.5 + distancia * 1000) / 1000 + " km");
                        setText(InfoAltitud, altitud + " m");
                        if (diffTMedia > 5000)
                            setText(InfoMedia, velocidadMedia + " km/h");
                        else
                            setText(InfoMedia, "-----");


                    }
                }
            }

            catch (Exception e)
            {
                setText(EstadoGPS, e.ToString());
            }
            finally
            {
                if (serialPort.IsOpen)
                    serialPort.Close();
                offGPS();
                if(dataFile!=null)
                    dataFile.Close();
                dataFile = null;
                m_EventThreadStopped.Set();
                t = null;
            }
        }
        

        void setText(Control control, string str)
        {
            if (InvokeRequired)
            {
                Invoke(new SetText(setText), new object[] { control, str });
            }
            else
            {
                control.Text = str;
            }
        }

        void alerta(string texto)
        {
            if (InvokeRequired)
            {
                Invoke(new Alerta(alerta), new object[] { texto });
            }
            else
            {
                MessageBox.Show(texto);
            }
        }

        void onGPS()
        {
            if (InvokeRequired)
            {
                Invoke(new OnOffGPS(onGPS), null);
            }
            else
            {
                if (guardar)
                    transferMenu.Enabled = false;

                gpsBtn.Text = "Desconectar";
                gpsBtn.ForeColor = Color.Blue;
                pictureGPSOk.Show();
                pictureGPSNok.Hide();
                conBtn.Enabled = true;
            }
        }

        void offGPS()
        {
            if (InvokeRequired)
            {
                this.Invoke(new OnOffGPS(offGPS), null);
            }
            else
            {
                closeDataFile = true;

                gpsBtn.Text = "Conectar";
                gpsBtn.ForeColor = Color.Black;
                pictureGPSNok.Show();
                pictureGPSOk.Hide();

                EstadoGPS.Text = "GPS DESCONECTADO";
                conBtn.Text = "Enviar";
                conBtn.ForeColor = Color.Black;
                enviar = false;
                conBtn.Enabled = false;
                pictureCONNok.Show();
                pictureCONOk.Hide();

                transferMenu.Enabled = true;
            }
        }

        void onTX()
        {
            if (InvokeRequired)
            {
                Invoke(new OnOffGPS(onTX), null);
            }
            else
            {
                enviar = true;
                conBtn.Text = "Detener";
                conBtn.ForeColor=Color.Red;
                pictureCONOk.Show();
                pictureGPSNok.Hide();
            }
        }

        void offTX()
        {
            if (InvokeRequired)
            {
                Invoke(new OnOffGPS(offTX), null);
            }
            else
            {
                enviar = false;
                conBtn.Text = "Enviar";
                conBtn.ForeColor = Color.Black;
                pictureCONNok.Show();
                pictureCONOk.Hide();
            }
        }

        private void textBox1_TextChanged_1(object sender, EventArgs e)
        {

        }

        private void button3_Click(object sender, EventArgs e)
        {
            if (enviar)
            {
                offTX();
            }
            else
            {
                iniciarTX = true;
            }
        }

        private void button1_Click_1(object sender, EventArgs e)
        {
            distancia = 0;
            diffTMedia = 0;
            InfoDistancia.Text = "0 km";
            InfoMedia.Text = "-----";
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            setText(timeLabel, DateTime.Now.ToShortTimeString());
        }

        private void HoraTimer_Tick(object sender, EventArgs e)
        {
            setText(timeLabel, DateTime.Now.ToShortTimeString());  
        }

        private void menuItem2_Click(object sender, EventArgs e)
        {
            string appFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
            string confFile = Path.Combine(appFolder, "config.xml");

            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load(confFile);
            XmlNode paramNode;

            if (guardar)
            {
                closeDataFile = true;

                guardar = false;
                guardarMenu.Text = "Activar";
                guardarMenu.Checked = false;

                paramNode = xmlDoc.SelectSingleNode("/config/param[@nombre='recordOn']");
                paramNode.Attributes["valor"].Value = "false";
                xmlDoc.Save(confFile);

                transferMenu.Enabled = true;
            }
            else
            {
                Hide();
                if (intervalFrm.ShowDialog() != DialogResult.Cancel)
                {
                    if (t != null)
                        transferMenu.Enabled = false;

                    interval = UInt32.Parse(intervalFrm.intervalCtrl.Text) * 1000;
                    guardar = true;
                    guardarMenu.Text = "Desactivar";
                    guardarMenu.Checked = true;

                    paramNode = xmlDoc.SelectSingleNode("/config/param[@nombre='interval']");
                    paramNode.Attributes["valor"].Value = intervalFrm.intervalCtrl.Text;
                    paramNode = xmlDoc.SelectSingleNode("/config/param[@nombre='recordOn']");
                    paramNode.Attributes["valor"].Value = "true";
                    xmlDoc.Save(confFile);
                }
                form1.Hide();
                Show();
            }
        }

        private void menuItem3_Click(object sender, EventArgs e)
        {
            DialogResult result;

            progressFrm.usuario = usuario;
            progressFrm.clave = clave;
            progressFrm.progressBar.Value = 0;
            Hide();
            result = progressFrm.ShowDialog();
            progressFrm.Hide();
            Show();
            if (result == DialogResult.OK)
            {
                alerta("Transferencia finalizada correctamente.");
            }
            else if (result == DialogResult.Retry)
            {
                alerta("ERROR. La transferencia se ha interrumpido. Vuelve a intentarlo.");
            }
        }

        private void menuItem5_Click(object sender, EventArgs e)
        {
            if (t != null)
            {
                m_EventStopThread.Set();
                while (!m_EventThreadStopped.WaitOne(0, false))
                    Application.DoEvents();
            }
            Application.Exit();
        }

        private void confMenu_Click(object sender, EventArgs e)
        {
            Hide();
            if (form1.ShowDialog() != DialogResult.Cancel)
            {
                usuario = form1.UsuarioCtrl.Text;
                clave = form1.ClaveCtrl.Text;
                refreshTime = UInt32.Parse(form1.refreshTimeCtrl.Text) * 1000;
                puerto = form1.puertoCtrl.Text;
                header = FormDatos.getDigets(usuario.ToLower() + "\x0000" + clave);
                System.Array.Copy(FormDatos.getDigets(clave), key, 16);
            }

            form1.Hide();
            Show();
        }

        private void menuItem2_Click_1(object sender, EventArgs e)
        {
//            Hide();
            acercaDeFrm.ShowDialog();
//            Show();
        }

    }
}
